/*
  Copyright (C) 2001  Dmitry V. Levin <ldv@altlinux.org>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#include <assert.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <unistd.h>

extern const char *__progname;

static void
my_error_print_progname (void)
{
	fflush (stdout);
	fprintf (stderr, "%s: ", __progname);
}

__attribute__ ((__noreturn__))
static void usage (void)
{
	fprintf (stderr, "usage: %s <pid> <command>\n", __progname);
	exit (EXIT_FAILURE);
}

static void
wait_for_pdeath (pid_t pid)
{
	for (;;)
	{
		if (kill (pid, 0) < 0)
		{
			if (ESRCH == errno)
				break;
			else
				error (EXIT_FAILURE, errno, "kill: %u", pid);
		}
		usleep (100000);
	}
}

static void
try_post_update_api (char *const *av)
{
	char  *script_name = getenv ("POST_UPDATE_SCRIPT");
	char  *pid_env     = getenv ("POST_UPDATE_PID");

	if (!script_name)
		return;

	/* Check that runner is alive. */
	if (!pid_env)
	{
		return;
	}
	else
	{
		pid_t pid = atoi (pid_env);

		if (kill (pid, 0) < 0)
			return;
	}

	/* Calc length of resulting command line. */
	ssize_t sz = 1; /* CR */
	size_t j;
	for (j = 0; av[j]; j++)
	{
		const char *p;

		if (j)
			sz++; /* Prepended separator. */
		sz += 2; /* Quotes. */
		for (p = av[j]; *p; p++)
			sz += (*p == '\'') ? 4 : 1;
	}

	char *buf = malloc (sz);
	if (!buf)
	{
		error (0, errno, "malloc");
		return;
	}

	/* Create shellescaped command line. */
	ssize_t i = 0;
	for (j = 0; av[j]; j++)
	{
		const char *p;

		if (j)
			buf[i++] = ' ';
		buf[i++] = '\'';
		for (p = av[j]; *p; p++)
		{
			if (*p == '\'')
			{
				buf[i++] = '\'';
				buf[i++] = '\\';
				buf[i++] = '\'';
				buf[i++] = '\'';
			}
			else
			{
				buf[i++] = *p;
			}
		}
		buf[i++] = '\'';
	}
	buf[i++] = '\n';
	assert (sz == i);

	/* Write command to the script. */
	const int fd = open (script_name, O_APPEND|O_WRONLY);
	if (fd == -1)
	{
		error (0, errno, "open");
		goto err_free;
	}
	/* Short commands should be atomic writes anyway. */
	if (flock (fd, LOCK_EX) == -1)
	{
		error (0, errno, "flock");
		goto err_close;
	}
	off_t pos = lseek (fd, 0, SEEK_END);
	if (pos == (off_t) -1)
		error (0, errno, "lseek");
	ssize_t n = write (fd, buf, i);
	if (n == -1)
	{
		error (0, errno, "write");
		goto err_close;
	}
	else if (n != i)
	{
		error (0, 0, "writing %zd bytes failed, written %zd", i, n);
		if (pos != (off_t) -1 &&
		    ftruncate(fd, pos) == -1)
			error (0, errno, "ftruncate");
		goto err_close;
	}

	close(fd);
	free(buf);
#ifdef DEBUG
	fprintf(stderr, "%s: Command queued to %s (pid %s).\n",
	    __progname, script_name, pid_env);
#endif
	exit(EXIT_SUCCESS);
err_close:
	close(fd);
err_free:
	free(buf);
	return;
}

int
main (int ac, char *const *av)
{
	pid_t   pid;

	error_print_progname = my_error_print_progname;

	if (ac < 3)
		usage ();

	pid = atoi (av[1]);

	/*
	 *  Check arguments.
	 */

	if (pid <= 1)
		usage ();

	/* Support for post-update command API. */
	try_post_update_api (av + 2);

	if (kill (pid, 0) < 0)
		error (EXIT_FAILURE, errno, "kill: %u", pid);

	/* Lets parent go on. */
	if (daemon (1, 1) < 0)
		error (EXIT_FAILURE, errno, "daemon");

	/* Wait for parent completion. */
	wait_for_pdeath (pid);

	execvp (av[2], av + 2);
	error (EXIT_FAILURE, errno, "execv: %s", av[2]);
	return EXIT_FAILURE;
}
